/*-------------------------------------------------------------------------
 *
 * array_expanded.c
 *      Basic functions for manipulating expanded arrays.
 *
 * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *      src/backend/utils/adt/array_expanded.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/tupmacs.h"
#include "utils/array.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"


/* "Methods" required for an expanded object */
static Size EA_get_flat_size(ExpandedObjectHeader *eohptr);
static void EA_flatten_into(ExpandedObjectHeader *eohptr,
                void *result, Size allocated_size);

static const ExpandedObjectMethods EA_methods =
{
    EA_get_flat_size,
    EA_flatten_into
};

/* Other local functions */
static void copy_byval_expanded_array(ExpandedArrayHeader *eah,
                          ExpandedArrayHeader *oldeah);


/*
 * expand_array: convert an array Datum into an expanded array
 *
 * The expanded object will be a child of parentcontext.
 *
 * Some callers can provide cache space to avoid repeated lookups of element
 * type data across calls; if so, pass a metacache pointer, making sure that
 * metacache->element_type is initialized to InvalidOid before first call.
 * If no cross-call caching is required, pass NULL for metacache.
 */
Datum
expand_array(Datum arraydatum, MemoryContext parentcontext,
             ArrayMetaState *metacache)
{// #lizard forgives
    ArrayType  *array;
    ExpandedArrayHeader *eah;
    MemoryContext objcxt;
    MemoryContext oldcxt;
    ArrayMetaState fakecache;

    /*
     * Allocate private context for expanded object.  We start by assuming
     * that the array won't be very large; but if it does grow a lot, don't
     * constrain aset.c's large-context behavior.
     */
    objcxt = AllocSetContextCreate(parentcontext,
                                   "expanded array",
                                   ALLOCSET_START_SMALL_SIZES);

    /* Set up expanded array header */
    eah = (ExpandedArrayHeader *)
        MemoryContextAlloc(objcxt, sizeof(ExpandedArrayHeader));

    EOH_init_header(&eah->hdr, &EA_methods, objcxt);
    eah->ea_magic = EA_MAGIC;

    /* If the source is an expanded array, we may be able to optimize */
    if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum)))
    {
        ExpandedArrayHeader *oldeah = (ExpandedArrayHeader *) DatumGetEOHP(arraydatum);

        Assert(oldeah->ea_magic == EA_MAGIC);

        /*
         * Update caller's cache if provided; we don't need it this time, but
         * next call might be for a non-expanded source array.  Furthermore,
         * if the caller didn't provide a cache area, use some local storage
         * to cache anyway, thereby avoiding a catalog lookup in the case
         * where we fall through to the flat-copy code path.
         */
        if (metacache == NULL)
            metacache = &fakecache;
        metacache->element_type = oldeah->element_type;
        metacache->typlen = oldeah->typlen;
        metacache->typbyval = oldeah->typbyval;
        metacache->typalign = oldeah->typalign;

        /*
         * If element type is pass-by-value and we have a Datum-array
         * representation, just copy the source's metadata and Datum/isnull
         * arrays.  The original flat array, if present at all, adds no
         * additional information so we need not copy it.
         */
        if (oldeah->typbyval && oldeah->dvalues != NULL)
        {
            copy_byval_expanded_array(eah, oldeah);
            /* return a R/W pointer to the expanded array */
            return EOHPGetRWDatum(&eah->hdr);
        }

        /*
         * Otherwise, either we have only a flat representation or the
         * elements are pass-by-reference.  In either case, the best thing
         * seems to be to copy the source as a flat representation and then
         * deconstruct that later if necessary.  For the pass-by-ref case, we
         * could perhaps save some cycles with custom code that generates the
         * deconstructed representation in parallel with copying the values,
         * but it would be a lot of extra code for fairly marginal gain.  So,
         * fall through into the flat-source code path.
         */
    }

    /*
     * Detoast and copy source array into private context, as a flat array.
     *
     * Note that this coding risks leaking some memory in the private context
     * if we have to fetch data from a TOAST table; however, experimentation
     * says that the leak is minimal.  Doing it this way saves a copy step,
     * which seems worthwhile, especially if the array is large enough to need
     * external storage.
     */
    oldcxt = MemoryContextSwitchTo(objcxt);
    array = DatumGetArrayTypePCopy(arraydatum);
    MemoryContextSwitchTo(oldcxt);

    eah->ndims = ARR_NDIM(array);
    /* note these pointers point into the fvalue header! */
    eah->dims = ARR_DIMS(array);
    eah->lbound = ARR_LBOUND(array);

    /* Save array's element-type data for possible use later */
    eah->element_type = ARR_ELEMTYPE(array);
    if (metacache && metacache->element_type == eah->element_type)
    {
        /* We have a valid cache of representational data */
        eah->typlen = metacache->typlen;
        eah->typbyval = metacache->typbyval;
        eah->typalign = metacache->typalign;
    }
    else
    {
        /* No, so look it up */
        get_typlenbyvalalign(eah->element_type,
                             &eah->typlen,
                             &eah->typbyval,
                             &eah->typalign);
        /* Update cache if provided */
        if (metacache)
        {
            metacache->element_type = eah->element_type;
            metacache->typlen = eah->typlen;
            metacache->typbyval = eah->typbyval;
            metacache->typalign = eah->typalign;
        }
    }

    /* we don't make a deconstructed representation now */
    eah->dvalues = NULL;
    eah->dnulls = NULL;
    eah->dvalueslen = 0;
    eah->nelems = 0;
    eah->flat_size = 0;

    /* remember we have a flat representation */
    eah->fvalue = array;
    eah->fstartptr = ARR_DATA_PTR(array);
    eah->fendptr = ((char *) array) + ARR_SIZE(array);

    /* return a R/W pointer to the expanded array */
    return EOHPGetRWDatum(&eah->hdr);
}

/*
 * helper for expand_array(): copy pass-by-value Datum-array representation
 */
static void
copy_byval_expanded_array(ExpandedArrayHeader *eah,
                          ExpandedArrayHeader *oldeah)
{
    MemoryContext objcxt = eah->hdr.eoh_context;
    int            ndims = oldeah->ndims;
    int            dvalueslen = oldeah->dvalueslen;

    /* Copy array dimensionality information */
    eah->ndims = ndims;
    /* We can alloc both dimensionality arrays with one palloc */
    eah->dims = (int *) MemoryContextAlloc(objcxt, ndims * 2 * sizeof(int));
    eah->lbound = eah->dims + ndims;
    /* .. but don't assume the source's arrays are contiguous */
    memcpy(eah->dims, oldeah->dims, ndims * sizeof(int));
    memcpy(eah->lbound, oldeah->lbound, ndims * sizeof(int));

    /* Copy element-type data */
    eah->element_type = oldeah->element_type;
    eah->typlen = oldeah->typlen;
    eah->typbyval = oldeah->typbyval;
    eah->typalign = oldeah->typalign;

    /* Copy the deconstructed representation */
    eah->dvalues = (Datum *) MemoryContextAlloc(objcxt,
                                                dvalueslen * sizeof(Datum));
    memcpy(eah->dvalues, oldeah->dvalues, dvalueslen * sizeof(Datum));
    if (oldeah->dnulls)
    {
        eah->dnulls = (bool *) MemoryContextAlloc(objcxt,
                                                  dvalueslen * sizeof(bool));
        memcpy(eah->dnulls, oldeah->dnulls, dvalueslen * sizeof(bool));
    }
    else
        eah->dnulls = NULL;
    eah->dvalueslen = dvalueslen;
    eah->nelems = oldeah->nelems;
    eah->flat_size = oldeah->flat_size;

    /* we don't make a flat representation */
    eah->fvalue = NULL;
    eah->fstartptr = NULL;
    eah->fendptr = NULL;
}

/*
 * get_flat_size method for expanded arrays
 */
static Size
EA_get_flat_size(ExpandedObjectHeader *eohptr)
{// #lizard forgives
    ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr;
    int            nelems;
    int            ndims;
    Datum       *dvalues;
    bool       *dnulls;
    Size        nbytes;
    int            i;

    Assert(eah->ea_magic == EA_MAGIC);

    /* Easy if we have a valid flattened value */
    if (eah->fvalue)
        return ARR_SIZE(eah->fvalue);

    /* If we have a cached size value, believe that */
    if (eah->flat_size)
        return eah->flat_size;

    /*
     * Compute space needed by examining dvalues/dnulls.  Note that the result
     * array will have a nulls bitmap if dnulls isn't NULL, even if the array
     * doesn't actually contain any nulls now.
     */
    nelems = eah->nelems;
    ndims = eah->ndims;
    Assert(nelems == ArrayGetNItems(ndims, eah->dims));
    dvalues = eah->dvalues;
    dnulls = eah->dnulls;
    nbytes = 0;
    for (i = 0; i < nelems; i++)
    {
        if (dnulls && dnulls[i])
            continue;
        nbytes = att_addlength_datum(nbytes, eah->typlen, dvalues[i]);
        nbytes = att_align_nominal(nbytes, eah->typalign);
        /* check for overflow of total request */
        if (!AllocSizeIsValid(nbytes))
            ereport(ERROR,
                    (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
                     errmsg("array size exceeds the maximum allowed (%d)",
                            (int) MaxAllocSize)));
    }

    if (dnulls)
        nbytes += ARR_OVERHEAD_WITHNULLS(ndims, nelems);
    else
        nbytes += ARR_OVERHEAD_NONULLS(ndims);

    /* cache for next time */
    eah->flat_size = nbytes;

    return nbytes;
}

/*
 * flatten_into method for expanded arrays
 */
static void
EA_flatten_into(ExpandedObjectHeader *eohptr,
                void *result, Size allocated_size)
{
    ExpandedArrayHeader *eah = (ExpandedArrayHeader *) eohptr;
    ArrayType  *aresult = (ArrayType *) result;
    int            nelems;
    int            ndims;
    int32        dataoffset;

    Assert(eah->ea_magic == EA_MAGIC);

    /* Easy if we have a valid flattened value */
    if (eah->fvalue)
    {
        Assert(allocated_size == ARR_SIZE(eah->fvalue));
        memcpy(result, eah->fvalue, allocated_size);
        return;
    }

    /* Else allocation should match previous get_flat_size result */
    Assert(allocated_size == eah->flat_size);

    /* Fill result array from dvalues/dnulls */
    nelems = eah->nelems;
    ndims = eah->ndims;

    if (eah->dnulls)
        dataoffset = ARR_OVERHEAD_WITHNULLS(ndims, nelems);
    else
        dataoffset = 0;            /* marker for no null bitmap */

    /* We must ensure that any pad space is zero-filled */
    memset(aresult, 0, allocated_size);

    SET_VARSIZE(aresult, allocated_size);
    aresult->ndim = ndims;
    aresult->dataoffset = dataoffset;
    aresult->elemtype = eah->element_type;
    memcpy(ARR_DIMS(aresult), eah->dims, ndims * sizeof(int));
    memcpy(ARR_LBOUND(aresult), eah->lbound, ndims * sizeof(int));

    CopyArrayEls(aresult,
                 eah->dvalues, eah->dnulls, nelems,
                 eah->typlen, eah->typbyval, eah->typalign,
                 false);
}

/*
 * Argument fetching support code
 */

/*
 * DatumGetExpandedArray: get a writable expanded array from an input argument
 *
 * Caution: if the input is a read/write pointer, this returns the input
 * argument; so callers must be sure that their changes are "safe", that is
 * they cannot leave the array in a corrupt state.
 */
ExpandedArrayHeader *
DatumGetExpandedArray(Datum d)
{
    /* If it's a writable expanded array already, just return it */
    if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
    {
        ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d);

        Assert(eah->ea_magic == EA_MAGIC);
        return eah;
    }

    /* Else expand the hard way */
    d = expand_array(d, CurrentMemoryContext, NULL);
    return (ExpandedArrayHeader *) DatumGetEOHP(d);
}

/*
 * As above, when caller has the ability to cache element type info
 */
ExpandedArrayHeader *
DatumGetExpandedArrayX(Datum d, ArrayMetaState *metacache)
{
    /* If it's a writable expanded array already, just return it */
    if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d)))
    {
        ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d);

        Assert(eah->ea_magic == EA_MAGIC);
        /* Update cache if provided */
        if (metacache)
        {
            metacache->element_type = eah->element_type;
            metacache->typlen = eah->typlen;
            metacache->typbyval = eah->typbyval;
            metacache->typalign = eah->typalign;
        }
        return eah;
    }

    /* Else expand using caller's cache if any */
    d = expand_array(d, CurrentMemoryContext, metacache);
    return (ExpandedArrayHeader *) DatumGetEOHP(d);
}

/*
 * DatumGetAnyArray: return either an expanded array or a detoasted varlena
 * array.  The result must not be modified in-place.
 */
AnyArrayType *
DatumGetAnyArray(Datum d)
{
    ExpandedArrayHeader *eah;

    /*
     * If it's an expanded array (RW or RO), return the header pointer.
     */
    if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(d)))
    {
        eah = (ExpandedArrayHeader *) DatumGetEOHP(d);
        Assert(eah->ea_magic == EA_MAGIC);
        return (AnyArrayType *) eah;
    }

    /* Else do regular detoasting as needed */
    return (AnyArrayType *) PG_DETOAST_DATUM(d);
}

/*
 * Create the Datum/isnull representation of an expanded array object
 * if we didn't do so previously
 */
void
deconstruct_expanded_array(ExpandedArrayHeader *eah)
{
    if (eah->dvalues == NULL)
    {
        MemoryContext oldcxt = MemoryContextSwitchTo(eah->hdr.eoh_context);
        Datum       *dvalues;
        bool       *dnulls;
        int            nelems;

        dnulls = NULL;
        deconstruct_array(eah->fvalue,
                          eah->element_type,
                          eah->typlen, eah->typbyval, eah->typalign,
                          &dvalues,
                          ARR_HASNULL(eah->fvalue) ? &dnulls : NULL,
                          &nelems);

        /*
         * Update header only after successful completion of this step.  If
         * deconstruct_array fails partway through, worst consequence is some
         * leaked memory in the object's context.  If the caller fails at a
         * later point, that's fine, since the deconstructed representation is
         * valid anyhow.
         */
        eah->dvalues = dvalues;
        eah->dnulls = dnulls;
        eah->dvalueslen = eah->nelems = nelems;
        MemoryContextSwitchTo(oldcxt);
    }
}
